package net.yegong.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.HeadlessException;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.UIManager;

import net.yegong.FileTypeCheck;
import net.yegong.Logger;
import net.yegong.Reader;
import net.yegong.Unzip;
import net.yegong.Zip;
import net.yegong.FileTypeCheck.FileType;

public class MainFrame extends JFrame {

	/**
	 * 
	 */
	private static final long serialVersionUID = -2230305869873845719L;

	public MainFrame() throws HeadlessException {
		super("利用多元统计分析对遥感图像进行有损压缩");

		setupSizeAndLocation();
		setupLookAndFeel();
		setupCloseOperation();
		setupComponents();
		addDroppable(this);
	}

	private void setupLookAndFeel() {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}

	private JTextField fileField;
	private JTextArea infoArea;
	private JButton workButton;
	private boolean isZip;
	private boolean isWorking;
	private final boolean isBinSourceEnabled = true;

	private final DropTargetListener dropListener = new DropTargetAdapter() {
		@Override
		public void dragEnter(DropTargetDragEvent dtde) {
			onDropEnter(dtde);
		}

		@Override
		public void drop(DropTargetDropEvent dtde) {
			onDrop(dtde);
		}

	};

	private void addDroppable(Component component) {
		new DropTarget(component, DnDConstants.ACTION_COPY_OR_MOVE, dropListener);
		if (component instanceof Container) {
			Container container = (Container) component;
			Component[] children = container.getComponents();
			for (Component c : children) {
				addDroppable(c);
			}
		}
	}

	private void setupComponents() {
		JButton fileButton = new JButton("File...");
		fileButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				onSelect();
			}
		});
		fileButton.setMaximumSize(new Dimension(100, 40));

		fileField = new JTextField();
		fileField.setEditable(false);
		fileField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 25));

		infoArea = new JTextArea();
		infoArea.setBorder(BorderFactory.createEtchedBorder());
		infoArea.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
		infoArea.setBackground(Color.LIGHT_GRAY);
		infoArea.setEditable(false);
		new LoggerThread().start();

		workButton = new JButton("Zip / Unzip");
		workButton.setEnabled(false);
		workButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				onWork();
			}

		});

		//
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
		panel.add(Box.createVerticalStrut(4));
		Box row;
		// start row 1
		row = Box.createHorizontalBox();
		row.add(Box.createHorizontalStrut(4));
		row.add(fileField);
		row.add(Box.createHorizontalStrut(10));
		row.add(fileButton);
		row.add(Box.createHorizontalStrut(10));
		row.add(workButton);
		row.add(Box.createHorizontalStrut(4));
		panel.add(row);
		// end row 1
		panel.add(Box.createVerticalStrut(4));
		// start row 2
		row = Box.createHorizontalBox();
		row.add(Box.createHorizontalStrut(4));
		JScrollPane scrollPane = new JScrollPane(infoArea);
		scrollPane.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
		row.add(scrollPane);
		row.add(Box.createHorizontalStrut(4));
		panel.add(row);
		// end row 2
		panel.add(Box.createVerticalStrut(4));
		add(panel);
	}

	protected void onWork() {
		isWorking = true;
		workButton.setEnabled(false);
		if (isZip) {
			new ZipThread().start();
		}
		else {
			new UnzipThread().start();
		}
	}

	private void setupSizeAndLocation() {
		Toolkit kit = Toolkit.getDefaultToolkit();
		Dimension screen = kit.getScreenSize();
		int width = screen.width / 2;
		int height = screen.height / 2;
		setSize(width, height);
//		setResizable(false);
		int spareWidth = screen.width - width;
		int spareHeight = screen.height - height;
		setLocation(spareWidth / 2, spareHeight / 2);
	}

	private void onSelect() {
		JFileChooser fileDialog = new JFileChooser();
		String filename = fileField.getText();
		if (!filename.isEmpty()) {
			fileDialog.setSelectedFile(new File(filename));
		}
		else {
			fileDialog.setSelectedFile(new File(".").getAbsoluteFile());
		}
		int result = fileDialog.showOpenDialog(MainFrame.this);
		if (result == JFileChooser.APPROVE_OPTION) {
			filename = fileDialog.getSelectedFile().getAbsolutePath();
			setFile(filename);
		}
	}

	public void setFile(String filename) {
		fileField.setText(filename);
		onFileSelected();
	}

	private void onFileSelected() {
		workButton.setText("Zip / Unzip");
		workButton.setEnabled(false);
		try {
			File file = new File(fileField.getText());
			if (!file.isFile()) {
				Logger.log("Path [%s] is not a valid file path.", file.toString());
			}
			else {
				Logger.log("Start read the meta info from file [%s].", file.toString());
				FileType type = FileTypeCheck.detect(file);
				if (FileType.UNKNOWN == type) {
					Logger.log("File [%s] is not a valid file for zip/unzip.", file.toString());
				}
				else if (FileType.IMG == type) {
					Logger.log("A valid HFA/Erdas Imagine Images file found.");
					isZip = true;
					workButton.setText("Zip");
					workButton.setEnabled(true);
				}
				else if (FileType.BIN == type) {
					Logger.log("A valid PCA-RS zip file found.");
					isZip = false;
					workButton.setText("Unzip");
					workButton.setEnabled(true);
				}
			}
		}
		catch (Exception e) {
			Logger.log("Fail to open the selected file");
			Logger.log(e.getMessage());
		}
	}

	private void onClose() {
		if (isWorking) {
			int ret = JOptionPane.showConfirmDialog(MainFrame.this, "The program is now working, sure to quit?", "Information", JOptionPane.YES_NO_OPTION);
			if (JOptionPane.YES_OPTION == ret) {
				System.exit(0);
			}
			else {
				setVisible(true);
				validate();
				repaint();
			}
		}
		else {
			System.exit(0);
		}
	}

	private void setupCloseOperation() {
		setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				onClose();
			}
		});
	}

	private class LoggerThread extends Thread {
		@Override
		public void run() {
			while (!isInterrupted()) {
				String message = Logger.getMessage();
				if (null != message) {
					String text = infoArea.getText();
					String time = new SimpleDateFormat("[HH:mm:ss]\n").format(new Date());
					text += time;
					text += message;
					text += "\n\n";
					infoArea.setText(text);
				}
			}
		}
	}

	private File generateFile(File inFile, String oldSuffix, String newSuffix) {
		String inFilename = inFile.getName();
		String outFilename;
		if (inFilename.endsWith('.' + oldSuffix)) {
			outFilename = inFilename.replaceFirst(oldSuffix + '$', newSuffix);
		}
		else {
			outFilename = inFilename + '.' + newSuffix;
		}
		File parent = inFile.getParentFile();
		return new File(parent + File.separator + outFilename);
	}

	private class ZipThread extends Thread {
		@Override
		public void run() {
			try {
				File inFile = new File(fileField.getText());
				File outFile = generateFile(inFile, "img", "rszip");
				Logger.log("The output file for zip is [%s]", outFile.toString());
				Logger.log("--- Start reading data ---");
				Reader reader = new Reader(inFile, 10000);
				reader.read();
				if (isBinSourceEnabled) {
					Logger.log("--- Convert source file to generic binary ---");
					File binSourceFile = generateFile(inFile, "img", "source.bin");
					OutputStream os = null;
					try {
						os = new BufferedOutputStream(new FileOutputStream(binSourceFile));
						byte[] data = reader.getData();
						for (int i = 0; i < data.length; i += 10000) {
							int len = Math.min(data.length - i, 10000);
							os.write(data, i, len);
						}
						os.flush();
					}
					finally {
						if (null != os) {
							os.close();
						}
					}
				}
				OutputStream fos = new FileOutputStream(outFile);
				FileTypeCheck.check(fos);
				ObjectOutputStream os = null;
				try {
					os = new ObjectOutputStream(new BufferedOutputStream(new GZIPOutputStream(fos)));
					Logger.log("--- Start zip ---");
					Zip.zip(reader, os, 0.99f);
					Logger.log("--- Finish zip ---");
				}
				finally {
					if (os != null) {
						os.close();
					}
				}
			}
			catch (Exception e) {
				e.printStackTrace();
				Logger.log("Fail to zip file.");
			}
			catch (OutOfMemoryError e) {
				e.printStackTrace();
				Logger.log("Fault error occurs because the shortage of memory.");
			}
			finally {
				System.gc();
				workButton.setEnabled(true);
				isWorking = false;
			}
		}
	}

	private class UnzipThread extends Thread {
		@Override
		public void run() {
			try {
				File inFile = new File(fileField.getText());
				File outFile = generateFile(inFile, "rszip", "output.bin");
				Logger.log("The output file for unzip is [%s]", outFile.toString());
				Logger.log("--- Start reading data ---");

				OutputStream os = null;
				ObjectInputStream is = null;
				try {
					Logger.log("--- Start unzip ---");
					InputStream fis = new FileInputStream(inFile);
					FileTypeCheck.check(fis);
					is = new ObjectInputStream(new BufferedInputStream(new GZIPInputStream(fis)));
					os = new BufferedOutputStream(new FileOutputStream(outFile));
					Unzip.unzip(is, os);
					Logger.log("--- Finish unzip ---");
				}
				finally {
					if (null != os) {
						os.close();
					}
					if (null != is) {
						is.close();
					}
				}
			}
			catch (Exception e) {
				e.printStackTrace();
				Logger.log("Fail to zip file.");
			}
			finally {
				workButton.setEnabled(true);
				isWorking = false;
			}
		}
	}

	private void onDropEnter(DropTargetDragEvent dtde) {
		try {
			Transferable tr = dtde.getTransferable();

			if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
				dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
				List<?> list = (List<?>) (tr.getTransferData(DataFlavor.javaFileListFlavor));
				if (list.size() == 1) {
					File file = (File) list.get(0);
					if (!file.isFile()) {
						dtde.rejectDrag();
					}
				}
				else {
					dtde.rejectDrag();
				}
			}
			else {
				dtde.rejectDrag();
			}
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
		catch (UnsupportedFlavorException ufe) {
			ufe.printStackTrace();
		}
	}

	private void onDrop(DropTargetDropEvent dtde) {
		try {
			Transferable tr = dtde.getTransferable();

			if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
				dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
				List<?> list = (List<?>) (tr.getTransferData(DataFlavor.javaFileListFlavor));
				File file = (File) list.get(0);
				setFile(file.getAbsolutePath());
				dtde.dropComplete(true);
			}
			else {
				dtde.rejectDrop();
			}
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
		catch (UnsupportedFlavorException ufe) {
			ufe.printStackTrace();
		}
	}
}
